﻿using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Web;

namespace ExtremeSwank.Authentication.OpenID.Plugins.Extensions
{
    /// <summary>
    /// Extension plugin that provides support for the OpenID Attribute Exchange extension.
    /// </summary>
    [Serializable]
    public class AttributeExchange : IExtension
    {
        private StateContainer _Parent;
        private static readonly string prefix = "openid.ax.";
        private AttributeExchangeMode _Mode;
        private List<AttributeExchangeItem> _RequestData;
        private string _UpdateUrl;

        /// <summary>
        /// Creates a new instance of AttributeExchange.
        /// </summary>
        /// <param name="state">The StateContainer object to attach.</param>
        public AttributeExchange(StateContainer state)
        {
            _Parent = state;
            state.RegisterPlugin(this);
            Mode = AttributeExchangeMode.Fetch;
            _RequestData = new List<AttributeExchangeItem>();
        }

        /// <summary>
        /// Creates a new instance of AttributeExchange.
        /// </summary>
        /// <param name="openid">The OpenIDConsumer object to attach.</param>
        public AttributeExchange(OpenIDConsumer openid)
        {
            _Parent = openid.StateContainer;
            _Parent.RegisterPlugin(this);
            Mode = AttributeExchangeMode.Fetch;
            _RequestData = new List<AttributeExchangeItem>();
        }

        /// <summary>
        /// The Update URL that the OpenID Provider will send updates to whenever the requested
        /// data is updated.
        /// </summary>
        /// <remarks><para>This is a feature that is supported by some OpenID Providers. Any time the
        /// data in this request is updated at the OpenID Provider, an update will be pushed to
        /// the URL supplied here.  If the OpenID Provider does not support this function, the 
        /// request is simply ignored.</para>
        /// <para>If you wish to receive these updates, use AXReceiver.</para>
        /// </remarks>
        public string UpdateUrl
        {
            get { return _UpdateUrl; }
            set { _UpdateUrl = value; }
        }

        /// <summary>
        /// Gets or sets the mode that will be used for this request.  Either a fetch or a store request.
        /// </summary>
        public AttributeExchangeMode Mode
        {
            get { return _Mode; }
            set { _Mode = value; }
        }

        /// <summary>
        /// Add an item to a Fetch request.
        /// </summary>
        /// <param name="iduri">The namespace URI for this item.</param>
        /// <param name="alias">A short alias to use for this item.  Use the same alias to retrieve data from the OpenIDUser object.</param>
        /// <param name="count">The number of values to return, if more than one value is recorded at the OpenID Provider.</param>
        /// <param name="required">Whether or not a response for this item is required.</param>
        public void AddFetchItem(string iduri, string alias, int count, bool required)
        {
            if (Mode == AttributeExchangeMode.Store) { throw new Exception("Incorrect Mode. Must set Mode to AttributeExchangeMode.Fetch to add fetch items."); }
            AttributeExchangeItem aei = new AttributeExchangeItem();
            aei.IdUri = iduri;
            aei.Alias = alias;
            aei.Count = count;
            aei.Required = required;
            _RequestData.Add(aei);
        }

        /// <summary>
        /// Add an item to a Fetch request.
        /// </summary>
        /// <param name="aeri">SchemaDef representing the value to fetch.</param>
        /// <param name="count">The number of values to return, if more than one value is recorded at the OpenID Provider.</param>
        /// <param name="required">Whether or not a response for this item is required.</param>
        public void AddFetchItem(SchemaDef aeri, int count, bool required)
        {
            AddFetchItem(aeri.Uri, aeri.Alias, count, required);
        }

        /// <summary>
        /// Add an item to a Store request.
        /// </summary>
        /// <param name="iduri">The namespace URI for this item.</param>
        /// <param name="alias">A short alias for this item.</param>
        /// <param name="values">One or more values to record in this item.</param>
        public void AddStoreItem(string iduri, string alias, params string[] values)
        {
            if (Mode == AttributeExchangeMode.Fetch) { throw new Exception("Incorrect Mode. Must set Mode to AttributeExchangeMode.Store to add store items."); }
            AttributeExchangeItem aei = new AttributeExchangeItem();
            aei.IdUri = iduri;
            aei.Alias = alias;
            aei.Count = values.Length;
            aei.Values = values;
            _RequestData.Add(aei);
        }

        /// <summary>
        /// Add an item to a Store request.
        /// </summary>
        /// <param name="aeri">SchemaDef representing the value(s) to store.</param>
        /// <param name="values">One or more values to record in this item.</param>
        public void AddStoreItem(SchemaDef aeri, params string[] values)
        {
            AddStoreItem(aeri.Uri, aeri.Alias, values);
        }

        #region IExtension Members

        /// <summary>
        /// Gets the human-readable name of this extension.
        /// </summary>
        /// <remarks>Always returns "OpenID Attribute Exchange 1.0".</remarks>
        public string Name
        {
            get { return "OpenID Attribute Exchange 1.0"; }
        }

        /// <summary>
        /// Gets the parent StateContainer object.
        /// </summary>
        public StateContainer Parent
        {
            get { return _Parent; }
        }

        /// <summary>
        /// Gets the namespace URI for this extension.
        /// </summary>
        /// <remarks>See <see cref="OpenIDNamespaces"/> for more information.</remarks>
        public string Namespace
        {
            get { return OpenIDNamespaces.AttributeExchange_1_0; }
        }

        /// <summary>
        /// Gets the key-value data to be sent to Identity Provider during
        /// authentication request.
        /// </summary>
        public Dictionary<string, string> AuthorizationData
        {
            get
            {
                Dictionary<string, string> pms = new Dictionary<string, string>();
                pms["openid.ns.ax"] = Namespace;
                switch (Mode)
                {
                    case AttributeExchangeMode.Fetch:
                        pms[prefix + "mode"] = "fetch_request";
                        List<string> required = new List<string>();
                        List<string> if_available = new List<string>();
                        foreach (AttributeExchangeItem aei in _RequestData)
                        {
                            pms[prefix + "type." + aei.Alias] = aei.IdUri;
                            if (aei.Count > 1)
                            {
                                pms[prefix + "count." + aei.Alias] = aei.Count.ToString();
                            }
                            if (aei.Required == true)
                            {
                                required.Add(aei.Alias);
                            }
                            else
                            {
                                if_available.Add(aei.Alias);
                            }
                        }
                        pms[prefix + "required"] = String.Join(",", required.ToArray());
                        pms[prefix + "if_available"] = String.Join(",", if_available.ToArray());
                        if (!String.IsNullOrEmpty(UpdateUrl))
                        {
                            pms[prefix + "update_url"] = UpdateUrl;
                        }
                        break;
                    case AttributeExchangeMode.Store:
                        pms[prefix + "mode"] = "store_request";
                        foreach (AttributeExchangeItem aei in _RequestData)
                        {
                            pms[prefix + "type." + aei.Alias] = aei.IdUri;
                            if (aei.Values != null)
                            {
                                if (aei.Count == 1)
                                {
                                    pms[prefix + "value." + aei.Alias] = aei.Values[0];
                                }
                                else if (aei.Count > 1)
                                {
                                    pms[prefix + "count." + aei.Alias] = aei.Count.ToString();
                                    for (int i = 0; i < aei.Count; i++)
                                    {
                                        pms[prefix + "value." + aei.Alias + "." + i] = aei.Values[i];
                                    }
                                }
                            }
                        }
                        break;
                }
                return pms;
            }
        }

        /// <summary>
        /// Whether or not the validation completed per this extension.
        /// </summary>
        /// <returns>Always returns true.</returns>
        public bool Validation()
        {
            return true;
        }

        /// <summary>
        /// Gets data for use by OpenIDUser object.
        /// </summary>
        public Dictionary<string, string> ObjectUserData
        {
            get
            {
                Dictionary<string, string> ReturnData = new Dictionary<string, string>();
                NameValueCollection Request = Parent.RequestArguments;
                foreach (string key in Request.Keys)
                {
                    if (key != null && key.StartsWith(prefix))
                    {
                        if (Request[key] != null)
                        {
                            ReturnData[key] = Request[key];
                        }
                    }
                }
                return ReturnData;
            }
        }

        /// <summary>
        /// Decode data returned from a fetch request
        /// </summary>
        /// <param name="arguments">Request data that holds the data to decode.</param>
        /// <returns>An array of AttributeExchangeItem objects.</returns>
        public static AttributeExchangeItem[] DecodeUserData(NameValueCollection arguments)
        {
            Dictionary<string, string> ds = Utility.GetExtNamespaceAliases(arguments);
            if (!ds.ContainsKey(OpenIDNamespaces.AttributeExchange_1_0)) { return null; }
            string p = ds[OpenIDNamespaces.AttributeExchange_1_0];
            string prefix = "openid." + p + ".";

            List<AttributeExchangeItem> list = new List<AttributeExchangeItem>();
            List<string> required = new List<string>();
            List<string> if_available = new List<string>();

            List<string> keys = new List<string>();

            foreach (string k in arguments.Keys)
            {
                keys.Add(k);
            }

            foreach (string s in arguments.Keys)
            {
                if (s != null && s.StartsWith(prefix + ".type."))
                {
                    AttributeExchangeItem aei = new AttributeExchangeItem();

                    // Determine alias
                    aei.Alias = s.Substring((prefix + ".type.").Length);

                    // Get URI
                    aei.IdUri = arguments[s];

                    // Determine value count
                    if (keys.Contains(prefix + "count." + aei.Alias))
                    {
                        aei.Count = Convert.ToInt32(arguments[prefix + "count." + aei.Alias]);
                    }
                    else
                    {
                        aei.Count = 1;
                    }

                    // Get values
                    List<string> values = new List<string>();
                    for (int i = 1; i <= aei.Count; i++)
                    {
                        values.Add(arguments[prefix + "value." + aei.Alias + "." + i]);
                    }
                    aei.Values = values.ToArray();

                    list.Add(aei);
                }
            }
            return list.ToArray();
        }

        /// <summary>
        /// Decode data returned from a fetch request
        /// </summary>
        /// <returns>An array of AttributeExchangeItem objects</returns>
        public AttributeExchangeItem[] DecodeUserData()
        {
            return DecodeUserData(Parent.RequestArguments);
        }

        #endregion
    }
}
